/*
 * Copyright (c) [2023] Huawei Technologies Co.,Ltd.All rights reserved.
 *
 * OpenArkCompiler is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR
 * FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include "merge_the_same_calls.h"

namespace maple {
bool MergeTheSameCalls::IsTheSameOpnd(const BaseNode &opnd1, const BaseNode &opnd2,
                                      const CallReturnVector &vec1, const CallReturnVector &vec2) const {
  if (opnd1.IsSameContent(&opnd2)) {
    return true;
  }
  if (opnd1.GetOpCode() != OP_dread || opnd2.GetOpCode() != OP_dread) {
    return false;
  }
  auto &dread1 = static_cast<const DreadNode&>(opnd1);
  auto &dread2 = static_cast<const DreadNode&>(opnd2);
  bool isSame = false;
  for (size_t i = 0; i < vec1.size(); ++i) {
    if (dread1.GetStIdx() != vec1[i].first) {
      continue;
    }
    if (dread1.GetFieldID() != vec1[i].second.GetFieldID()) {
      return false;
    }
    if (dread2.GetStIdx() != vec2[i].first ||
        dread2.GetFieldID() != vec2[i].second.GetFieldID()) {
      return false;
    }
    isSame = true;
    break;
  }
  return isSame;
}

bool MergeTheSameCalls::IsTheSameDass(const StmtNode &stmt1, const StmtNode &stmt2,
                                      const CallReturnVector &vec1, const CallReturnVector &vec2) const {
  auto &dass1 = static_cast<const DassignNode&>(stmt1);
  auto &dass2 = static_cast<const DassignNode&>(stmt2);
  auto rhs1 = static_cast<DreadNode*>(dass1.GetRHS());
  auto rhs2 = static_cast<DreadNode*>(dass2.GetRHS());
  return IsTheSameOpnd(*rhs1, *rhs2, vec1, vec2) &&
      (dass1.GetFieldID() == dass2.GetFieldID() && dass1.GetStIdx() == dass2.GetStIdx());
}

bool MergeTheSameCalls::IsTheSameStmts(const StmtNode &stmt1, const StmtNode &stmt2,
                                       const CallReturnVector &vec1, const CallReturnVector &vec2) const {
  if (stmt1.GetOpCode() != stmt2.GetOpCode()) {
    return false;
  }
  switch (stmt1.GetOpCode()) {
    case OP_dassign: {
      return IsTheSameDass(stmt1, stmt2, vec1, vec2);
    }
    case OP_if: {
      const IfStmtNode &ifStmtNode1 = static_cast<const IfStmtNode&>(stmt1);
      const IfStmtNode &ifStmtNode2 = static_cast<const IfStmtNode&>(stmt2);
      if (!ifStmtNode1.Opnd(0)->IsSameContent(ifStmtNode2.Opnd(0))) {
        return false;
      }
      if ((ifStmtNode1.GetThenPart() == nullptr && ifStmtNode2.GetThenPart() != nullptr) ||
          (ifStmtNode1.GetThenPart() != nullptr && ifStmtNode2.GetThenPart() == nullptr)) {
        return false;
      }
      if ((ifStmtNode1.GetElsePart() == nullptr && ifStmtNode2.GetElsePart() != nullptr) ||
          (ifStmtNode1.GetElsePart() != nullptr && ifStmtNode2.GetElsePart() == nullptr)) {
        return false;
      }
      if (ifStmtNode1.GetThenPart() != nullptr && ifStmtNode2.GetThenPart() != nullptr) {
        return IsTheSameStmts(*ifStmtNode1.GetThenPart(), *ifStmtNode2.GetThenPart(), vec1, vec2);
      }
      if (ifStmtNode1.GetElsePart() != nullptr && ifStmtNode2.GetElsePart() != nullptr) {
        return IsTheSameStmts(*ifStmtNode1.GetElsePart(), *ifStmtNode2.GetElsePart(), vec1, vec2);
      }
      return true;
    }
    case OP_return: {
      auto &ret1 = static_cast<const NaryStmtNode&>(stmt1);
      auto &ret2 = static_cast<const NaryStmtNode&>(stmt2);
      if (ret1.NumOpnds() != ret2.NumOpnds()) {
        return false;
      }
      for (size_t i = 0; i < ret1.NumOpnds(); ++i) {
        if (!IsTheSameOpnd(*ret1.Opnd(i), *ret2.Opnd(i), vec1, vec2)) {
          return false;
        }
      }
      return true;
    }
    case OP_iassign: {
      auto iass1 = static_cast<const IassignNode&>(stmt1);
      auto iass2 = static_cast<const IassignNode&>(stmt2);
      return (IsTheSameOpnd(*iass1.GetRHS(), *iass2.GetRHS(), vec1, vec2) &&
          IsTheSameOpnd(iass1.GetAddrExprBase(), iass2.GetAddrExprBase(), vec1, vec2)) &&
          iass1.GetTyIdx() == iass2.GetTyIdx() && iass1.GetFieldID() == iass2.GetFieldID() &&
          iass1.IsExpandedFromArrayOfCharFunc() == iass2.IsExpandedFromArrayOfCharFunc();
    }
    case OP_block: {
      const BlockNode &block1 = static_cast<const BlockNode&>(stmt1);
      const BlockNode &block2 = static_cast<const BlockNode&>(stmt2);
      const StmtNode *stmtNode1 = block1.GetFirst();
      const StmtNode *stmtNode2 = block2.GetFirst();
      while (stmtNode1 != nullptr && stmtNode2 != nullptr) {
        if (!IsTheSameStmts(*stmtNode1, *stmtNode2, vec1, vec2)) {
          return false;
        }
        stmtNode1 = stmtNode1->GetNext();
        stmtNode2 = stmtNode2->GetNext();
      }
      return (stmtNode1 == nullptr && stmtNode2 == nullptr);
    }
    case OP_goto: {
      const GotoNode &goto1 = static_cast<const GotoNode&>(stmt1);
      const GotoNode &goto2 = static_cast<const GotoNode&>(stmt2);
      return goto1.GetOffset() == goto2.GetOffset();
    }
    default:
      return false;
  }
}

bool MergeTheSameCalls::StmtListIsEqual(const StmtNode *stmt1, const StmtNode *stmt2,
                                        const CallReturnVector &vec1, const CallReturnVector &vec2) const {
  bool isEqual = true;
  while (stmt1 && stmt2) {
    if (!IsTheSameStmts(*stmt1, *stmt2, vec1, vec2)) {
      isEqual = false;
      break;
    }
    if (stmt1->GetOpCode() == OP_return || stmt2->GetOpCode() == OP_goto) {
      break;
    }
    stmt1 = stmt1->GetNext();
    stmt2 = stmt2->GetNext();
  }
  return isEqual;
}

bool MergeTheSameCalls::IsTheSameOpnds(const StmtNode &stmt1, const StmtNode &stmt2) const {
  if (stmt1.NumOpnds() != stmt2.NumOpnds()) {
    return false;
  }
  for (size_t i = 0; i < stmt2.NumOpnds(); ++i) {
    if (!stmt2.Opnd(i)->IsSameContent(stmt1.Opnd(i))) {
      return false;
    }
  }
  return true;
}

StmtNode *MergeTheSameCalls::DealWithCall(BlockNode &block, StmtNode &stmt) {
  auto &callNode = static_cast<CallNode&>(stmt);
  auto puIdx = callNode.GetPUIdx();
  auto it = callNodeCands.try_emplace(
      puIdx, std::unordered_map<CallNode*, BlockNode*>{ { &callNode, &block } });
  if (it.second) {
    return nullptr;
  }
  auto &retVar1 = callNode.GetReturnVec();
  for (auto &cand : it.first->second) {
    auto *callNode2 = static_cast<CallNode*>(cand.first);
    if (!IsTheSameOpnds(callNode, *callNode2)) {
      continue;
    }
    auto &retVar2 = callNode2->GetReturnVec();
    ASSERT(callNode.GetPUIdx() == callNode2->GetPUIdx(), "must be equal");
    if (callNode.GetTyIdx() != callNode2->GetTyIdx() ||
        retVar1.size() != retVar2.size()) {
      continue;
    }
    auto stmt1 = callNode.GetNext();
    auto stmt2 = callNode2->GetNext();
    if (!StmtListIsEqual(stmt1, stmt2, retVar1, retVar2)) {
      continue;
    }
    if (stmt1 != nullptr && stmt2 != nullptr &&
        (stmt1->GetOpCode() == OP_return || stmt1->GetOpCode() == OP_goto)) {
      LabelNode *label = nullptr;
      if (cand.first->GetPrev() == nullptr || cand.first->GetPrev()->GetOpCode() != OP_label) {
        LabelIdx labIdx = mod.GetMIRBuilder()->CreateLabIdx(*currFunc);
        label = mod.GetMIRBuilder()->CreateStmtLabel(labIdx);
        cand.second->InsertBefore(callNode2, label);
      } else {
        label = static_cast<LabelNode*>(cand.first->GetPrev());
      }
      auto gotoNode = mod.GetMIRBuilder()->CreateStmtGoto(OP_goto, label->GetLabelIdx());
      block.InsertBefore(&callNode, gotoNode);
      auto needRemove = &stmt;
      while (needRemove != stmt1) {
        auto currStmt = needRemove;
        needRemove = needRemove->GetNext();
        block.RemoveStmt(currStmt);
      }
      block.RemoveStmt(stmt1);
      return gotoNode;
    }
  }
  (void)it.first->second.emplace(std::make_pair(&callNode, &block));
  return nullptr;
}

StmtNode *MergeTheSameCalls::ProcessStmt(BlockNode &block, StmtNode &stmt) {
  switch (stmt.GetOpCode()) {
    case OP_call:
    case OP_callassigned: {
      return DealWithCall(block, stmt);
    }
    case OP_if: {
      IfStmtNode &ifStmtNode = static_cast<IfStmtNode&>(stmt);
      if (ifStmtNode.GetThenPart() != nullptr) {
        ProcessBlock(*ifStmtNode.GetThenPart());
      }
      if (ifStmtNode.GetElsePart() != nullptr) {
        ProcessBlock(*ifStmtNode.GetElsePart());
      }
      break;
    }
    case OP_while:
    case OP_dowhile: {
      WhileStmtNode &whileStmtNode = static_cast<WhileStmtNode&>(stmt);
      if (whileStmtNode.GetBody() != nullptr) {
        ProcessBlock(*whileStmtNode.GetBody());
      }
      break;
    }
    case OP_doloop: {
      DoloopNode &doloopNode = static_cast<DoloopNode&>(stmt);
      if (doloopNode.GetDoBody() != nullptr) {
        ProcessBlock(*doloopNode.GetDoBody());
      }
      break;
    }
    default: {
      break;
    }
  }
  return nullptr;
}

void MergeTheSameCalls::ProcessBlock(BlockNode &block) {
  StmtNode *next = nullptr;
  for (StmtNode *stmtNode = block.GetFirst(); stmtNode != nullptr; stmtNode = next) {
    auto *res = ProcessStmt(block, *stmtNode);
    next = (res != nullptr) ? res : stmtNode->GetNext();
  }
}

void MergeTheSameCalls::Run() {
  for (MIRFunction *func : std::as_const(mod.GetFunctionList())) {
    if (func == nullptr || func->GetBody() == nullptr) {
      continue;
    }
    callNodeCands.clear();
    mod.SetCurFunction(func);
    currFunc = func;
    ProcessBlock(*func->GetBody());
  }
}

bool M2MMergeTheSameCalls::PhaseRun(maple::MIRModule &m) {
  if (!Options::enableMergeCallNode) {
    return false;
  }
  MergeTheSameCalls merge(m);
  merge.Run();
  return true;
}

void M2MMergeTheSameCalls::GetAnalysisDependence(AnalysisDep &aDep) const {
  aDep.AddRequired<M2MCallGraph>();
  aDep.PreservedAllExcept<M2MCallGraph>();
}
}  // namespace maple